package logutil

import (
	"fmt"
	"runtime"
	"strconv"
	"strings"
	"sync"
)

const (
	mainPkgName = "main.main"
)

var stackPool = &sync.Pool{
	New: func() any {
		return &Stack{
			pcs: make([]uintptr, 32),
		}
	},
}

// code taken from https://github.com/pkg/error with modification

// Frame represents a program counter inside a stack frame.
// For historical reasons if Frame is interpreted as a uintptr
// its value represents the program counter + 1.
type Frame uintptr

// PC returns the program counter for this frame;
// multiple frames may have the same PC value.
func (f Frame) PC() uintptr { return uintptr(f) - 1 }

// stack represents a stack of program counters.
type Stack struct {
	pcs []uintptr
}

func (s Stack) String() string {
	var sb strings.Builder
	for i, v := range s.pcs {
		pc := Frame(v).PC()
		fn := runtime.FuncForPC(pc)
		if fn == nil {
			break
		}

		file, line := fn.FileLine(pc)
		fnName := fn.Name()
		fmt.Fprintf(&sb, "% 3d. %s()\n\t%s:%d\n", i+1, fnName, file, line)
		if fnName == mainPkgName {
			break
		}
	}

	return sb.String()
}

// 获取当前调用栈
func GetCallerStack(skip int) string {
	if skip < 0 {
		skip = 0
	}

	stack := stackPool.Get().(*Stack)
	defer stackPool.Put(stack)
	stack.pcs = make([]uintptr, 32)

	n := runtime.Callers(skip, stack.pcs)
	stack.pcs = stack.pcs[0:n]

	return stack.String()
}

// 获取调用函数及行号
func GetCaller(skip int) string {
	if skip < 0 {
		skip = 0
	}

	funcName := "???"
	pc, _, line, ok := runtime.Caller(1 + skip)
	if ok {
		if fn := runtime.FuncForPC(pc); fn != nil {
			funcName = fn.Name()
		}
	}

	return funcName + "():" + strconv.Itoa(line)
}
